from abc import ABC, abstractmethod
from dataclasses import dataclass
from enum import Enum

from im_key_event import ImKeyEvent


class ImState(Enum):
    CLOSED = 0  # 已关闭, 不响应任何请求
    READY = 1  # 就绪, 等待输入
    EDIT = 2  # 正在输入编码
    COMPOSING = 3  # 正在选字(组合候选)
    PAGING = 4  # 正在翻页


@dataclass
class Candidate(object):
    text: str = ''
    code: str = ''
    weight: int = 0

    def __str__(self) -> str:
        return '{}\t{}\t{}'.format(self.text or '<None>', self.code or '<None>', self.weight)


class CandidateList(object):
    def __init__(self) -> None:
        super().__init__()
        self._list = []

    def append(self, item: Candidate):
        # print('>>> append() item={}'.format(item))
        self._list.append(item)

    def clear(self):
        self._list.clear()

    def size(self) -> int:
        return len(self._list)

    def get(self, index: int):
        # print('<<< get() size={}, index={}'.format(self.size(), index))
        if 0 <= index < self.size():
            # print('<<< get() found.')
            return self._list[index]
        # print('<<< get() not found.')
        return Candidate()

    def removeAt(self, index: int):
        if 0 <= index < self.size():
            items = []
            for i in range(0, self.size()):
                if i == index:
                    continue
                items.append(self._list[i])
            self._list = items


class CandidateState(object):
    """
    界面(候选)状态，一般有：
    composition: str 已经确认的候选和编码
    candidateList: [] 候选列表
    code: str 正在查询的编码
    start: int 候选在所有查询结果中的序号，翻页时使用
    limit: int 本次查询限制的查询结果数，翻页时使用
    """

    def __init__(self, composition='') -> None:
        super().__init__()
        self.composition = composition
        self.candidateList = CandidateList()
        self.candidateCursor = 0
        self.start = 0
        self.limit = 9

    def hasComposition(self):
        return len(self.composition) > 0

    def hasCandidate(self):
        return self.count() > 0

    def moveToPrev(self):
        self.candidateCursor = max(self.candidateCursor - 1, 0)

    def moveToNext(self):
        if self.candidateCursor + 1 < self.count():
            self.candidateCursor += 1

    def candidateAt(self, index) -> Candidate:
        return self.candidateList.get(index)

    def candidateTextAt(self, index, comment_prefix='') -> str:
        item = self.candidateList.get(index)
        text = item.text
        if text and len(comment_prefix) > 0:
            code = item.code
            if len(self.composition) < len(code) and code.startswith(self.composition):
                comment = comment_prefix + code[len(self.composition):]
                text += comment
        return text

    def count(self):
        return self.candidateList.size()

    def reset(self):
        self.composition = ''
        self.candidateList.clear()
        self.candidateCursor = 0
        self.start = 0
        self.limit = 9

    def __str__(self) -> str:
        return 'composition:{}, count:{}'.format(self.composition, self.count())


class ImService(ABC):
    @abstractmethod
    def applyTheme(self, style):
        pass

    @abstractmethod
    def getWorkDir(self):
        pass

    @abstractmethod
    def getDataDir(self):
        pass

    @abstractmethod
    def showTooltip(self, text: str, timeout: int = 1):
        pass

    @abstractmethod
    def setComposition(self, composition: str):
        pass

    @abstractmethod
    def showCandidates(self, state: CandidateState):
        pass

    @abstractmethod
    def hideCandidates(self):
        pass

    @abstractmethod
    def commit(self, text=''):
        pass


class ImEngine(ABC):
    @abstractmethod
    def useImService(self, im_service: ImService):
        pass

    @abstractmethod
    def stop(self):
        pass

    @abstractmethod
    def onActivate(self):
        """
        输入法被激活
        """
        pass

    @abstractmethod
    def onDeactivate(self):
        """
        用户离开输入法
        """
        pass

    @abstractmethod
    def isComposing(self) -> bool:
        pass

    @abstractmethod
    def isPaging(self) -> bool:
        pass

    @abstractmethod
    def getMode(self, name: str):
        pass

    @abstractmethod
    def getModes(self) -> dict:
        pass

    @abstractmethod
    def testKeyDown(self, key_event: ImKeyEvent) -> bool:
        pass

    @abstractmethod
    def onKeyDown(self, key_event: ImKeyEvent):
        """
        处理用户的按键, 并作出响应(更新候选、上屏、清空编码...)
        """
        pass

    @abstractmethod
    def onKeyUp(self, key_event: ImKeyEvent):
        pass

    def onTap(self):
        pass

    @abstractmethod
    def execute(self, command: str, args=None):
        pass

    @abstractmethod
    def updateComposition(self):
        pass

    @abstractmethod
    def updateCandidate(self):
        pass

    @abstractmethod
    def commit(self, text=''):
        pass

    @abstractmethod
    def showTooltip(self, msg, timeout=1):
        pass


class ImEditor(ABC):
    @abstractmethod
    def accept(self, code: str) -> bool:
        pass

    @abstractmethod
    def maxLength(self) -> int:
        pass

    @abstractmethod
    def backspace(self):
        pass

    @abstractmethod
    def clear(self):
        pass


class ImTranslator(ABC):
    @abstractmethod
    def getPageSize(self) -> int:
        pass

    @abstractmethod
    def getStart(self) -> int:
        pass

    @abstractmethod
    def getEnd(self) -> int:
        pass

    @abstractmethod
    def translate(self, code: str, start: int = 0) -> CandidateList:
        pass

    @abstractmethod
    def pageUp(self) -> int:
        pass

    @abstractmethod
    def pageDown(self) -> int:
        pass


class ImEjector(ABC):
    @abstractmethod
    def eject(self, old_state: CandidateState, new_state: CandidateState, im_service: ImService) -> bool:
        pass
